// Package main 主程序入口
package main

import (
	"context"
	"flag"
	"fmt" // 导入 fmt 用于格式化字符串
	"os"
	"path/filepath"
	"runtime"
	"strconv" // 导入 strconv 用于整数转字符串
	"time"

	"px-table-checksum-go/checksum"
	"px-table-checksum-go/config" // 确保导入正确的包路径
	"px-table-checksum-go/db"     // 确保导入正确的包路径

	"github.com/sirupsen/logrus"
)

// initLogrus 初始化日志库
func initLogrus(levelStr string) {
	// 设置日志格式为文本格式
	logrus.SetFormatter(&logrus.TextFormatter{
		FullTimestamp: true, // 显示完整时间戳
		// 标准时间戳格式，包含毫秒
		TimestampFormat: "2006-01-02 15:04:05.000",
		// 强制颜色输出（在支持的终端中），方便阅读
		ForceColors: true,

		// 自定义调用者信息格式，转换为相对路径
		// f 是当前日志调用点的运行时帧信息
		CallerPrettyfier: func(f *runtime.Frame) (string, string) {
			// 获取函数名称，只保留函数的基本名称（不包含包路径等）
			// 如果想保留完整的函数名（如 main.main），可以使用 f.Function
			funcName := filepath.Base(f.Function)

			// 获取源文件路径和行号
			fileName := f.File
			lineNumber := f.Line

			// 尝试获取当前工作目录，用于计算相对路径
			wd, err := os.Getwd()
			var fileAndLine string

			if err == nil {
				// 尝试计算文件路径相对于工作目录的相对路径
				// filepath.Rel 会处理跨盘符等复杂情况
				if rel, err := filepath.Rel(wd, fileName); err == nil {
					// 成功获取相对路径
					fileName = rel
				}
				// 如果 filepath.Rel 失败，fileName 仍然是绝对路径
				// 这里我们选择即使失败也保留绝对路径（或已规范化的），并规范化路径分隔符
				fileName = filepath.ToSlash(fileName)
			} else {
				// 如果获取工作目录失败，直接使用原始文件路径并规范化分隔符
				logrus.Debugf("无法获取工作目录，使用绝对路径或文件基础名称: %v", err)
				fileName = filepath.ToSlash(fileName)
				// 或者可以选择只使用文件名：fileName = filepath.Base(fileName)
			}

			// 将行号从整数转换为字符串
			lineNumberStr := strconv.Itoa(lineNumber) // **FIXED: Use strconv.Itoa**

			// 组合 文件名:行号
			fileAndLine = fmt.Sprintf("%s:%s", fileName, lineNumberStr) // 使用 fmt.Sprintf 组合字符串

			// CallerPrettyfier 应该返回 (函数名, 文件名:行号)
			return funcName, fileAndLine
		},
	})

	// 设置日志输出到标准错误（通常是控制台）
	logrus.SetOutput(os.Stderr)

	// 解析配置的日志级别字符串
	level, err := logrus.ParseLevel(levelStr)
	if err != nil {
		// 如果配置的级别无效，则使用默认的 Info 级别，并打印警告
		logrus.Warnf("无效的日志级别 '%s'，将使用默认级别 'info': %v", levelStr, err)
		logrus.SetLevel(logrus.InfoLevel)
	} else {
		// 设置解析得到的日志级别
		logrus.SetLevel(level)
	}

	// 开启报告调用者信息，这是 CallerPrettyfier 生效的前提
	logrus.SetReportCaller(true)
}

func main() {
	// 命令行参数定义
	configFile := flag.String("config", "config/config.yaml", "配置文件路径")                         // 指定配置文件路径，默认值 "config/config.yaml"
	modeOverride := flag.Int("mode", -1, "运行模式覆盖 (0: 仅计算校验和, 1: 计算并比较, 2: 仅比较), -1 表示使用配置文件设置") // 覆盖模式，默认-1不覆盖
	flag.Parse()                                                                                // 解析命令行参数

	// 在加载配置前，先用默认 Info 级别初始化日志，确保配置加载过程中的日志能输出
	// 注意：这里初始化的日志级别可能会被后续配置中的 LogLevel 覆盖
	initLogrus("info")

	logrus.Info("程序启动...")
	logrus.Infof("使用配置文件: %s", *configFile) // 记录使用的配置文件路径
	startTime := time.Now()                 // 记录程序启动时间

	// 1. 加载配置 (现在使用Viper)
	if err := config.LoadConfig(*configFile); err != nil {
		// 使用 Fatalln 会在记录日志后调用 os.Exit(1) 退出程序
		logrus.Fatalln("加载配置失败:", err) // 配置加载失败是致命错误
	}

	// 配置加载完成后，根据配置的级别重新设置日志级别
	initLogrus(config.GlobalConfig.Options.LogLevel)                   // **使用加载的配置中的日志级别**
	logrus.Infof("日志级别已设置为: %s", config.GlobalConfig.Options.LogLevel) // 记录最终的日志级别

	// 如果命令行参数指定了模式，覆盖配置文件中的设置
	if *modeOverride >= 0 && *modeOverride <= 2 {
		logrus.Infof("使用命令行指定的模式: %d (覆盖配置文件中的 %d)", *modeOverride, config.GlobalConfig.Options.Mode)
		config.GlobalConfig.Options.Mode = *modeOverride // 应用命令行覆盖
	}

	// 2. 初始化数据库连接池
	if err := db.InitAllPools(); err != nil {
		logrus.Fatalln("初始化数据库连接池失败:", err) // 初始化失败是致命错误
	}
	// 使用 defer 确保程序退出时关闭所有数据库连接
	// defer 的函数会在 main 函数返回前执行
	defer db.GetDBManager().CloseAllPools()

	// 打印关键配置信息
	logrus.Infof("运行模式: %d", config.GlobalConfig.Options.Mode)
	logrus.Infof("Chunk 大小: %d", config.GlobalConfig.Options.ChunkSize)
	logrus.Infof("在 DB 计算 CRC32: %t", config.GlobalConfig.Options.CalcCRC32InDB)
	logrus.Infof("生成修复 SQL: %t", config.GlobalConfig.Options.GenerateFixSQL)
	// 对危险操作（自动执行SQL）使用Warn级别日志，突出风险
	logrus.Warnf("自动运行修复 SQL: %t (危险!)", config.GlobalConfig.Options.RunFixSQL)
	logrus.Infof("检查的表数量: %d", config.GetTablesToCheckCount()) // 从配置获取待检查表的总数
	logrus.Infof("CPU 核心数: %d", runtime.NumCPU())              // 记录可用的CPU核心数
	// 设置Go程序可以使用的最大CPU核心数，通常设置为所有可用核心以充分利用硬件性能
	runtime.GOMAXPROCS(runtime.NumCPU())

	// 3. 执行主流程：校验和计算与比较
	// 使用 context.WithCancel 创建一个可取消的上下文，用于控制整个操作的生命周期
	// 如果需要优雅地处理中断信号（如 Ctrl+C），可以在这里监听信号并调用 cancel()
	// 考虑到当前的示例是简单的运行至结束，这里直接调用 RunChecksumAndCompareWithContext
	// 如果不需要取消功能，可以直接调用 checksum.RunChecksumAndCompare()
	ctx, cancel := context.WithCancel(context.Background()) // 导入 context 包
	defer cancel()                                          // 确保在 main 函数退出时调用 cancel，释放资源

	// RunChecksumAndCompare 函数现在接受一个 context.Context 参数
	err := checksum.RunChecksumAndCompareWithContext(ctx) // 假设 checksum 包提供了带上下文的函数
	if err != nil {
		logrus.Errorf("执行过程中发生错误: %v", err) // 记录执行过程中发生的错误
		duration := time.Since(startTime)
		logrus.Infof("程序异常结束。总耗时: %s", duration) // 记录异常结束时的总耗时
		os.Exit(1)                               // 非正常退出，返回非零状态码
	}

	// 4. 正常结束
	duration := time.Since(startTime)        // 计算总执行时间
	logrus.Infof("程序执行完毕。总耗时: %s", duration) // 记录正常结束时的总耗时
	// os.Exit(0) 是默认行为，可以省略
}

// 需要在 checksum 和 db 包中确保日志记录也使用了相同的 logrus 实例，
// 并且调用日志函数的地方都能正确报告调用者信息。
// 通常只需要确保所有地方都使用 logrus 包的全局 Logger 实例（默认的 logrus.StandardLogger()）
// 并且在程序启动时（main 函数中）配置好 logrus 就行。
